/*
 *  Copyright 2008 Mark Ashworth <javameme@gmail.com>.
 * 
 *  Licensed under the GNU General Public License v3 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.gnu.org/licenses/gpl-3.0.html
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package xperiment.metaphor.model.entity;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import metaphor.service.ApplicationManager;
import metaphor.service.ServiceException;
import metaphor.persistence.Dao;
import metaphor.persistence.filter.Query;
import org.springframework.dao.DataAccessException;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import xperiment.metaphor.model.DomainObject;
import xperiment.metaphor.model.ModelException;
import xperiment.metaphor.model.ModelUtils;
import xperiment.metaphor.model.Persistent;
import xperiment.metaphor.model.RetrieveException;
import xperiment.metaphor.model.SaveException;
import xperiment.metaphor.model.UniqueResultException;
import xperiment.metaphor.model.XML;
import xperiment.metaphor.model.contact.Address;
import xperiment.metaphor.model.entity.Contact;
import xperiment.metaphor.model.entity.ContactType;
import xperiment.metaphor.model.entity.Employee;
import xperiment.metaphor.model.entity.Position;
import xperiment.metaphor.model.entity.User;
import xperiment.metaphor.model.entity.PersonActor;
import xperiment.metaphor.model.entity.Gender;
import xperiment.metaphor.model.entity.PopulationGroup;
import xperiment.metaphor.model.entity.Role;

/**
 * Person
 *
 * @author Mark Ashworth
 * @version 1.0.0
 * @since 1.0.0
 */
//@Entity
//@Inheritance(strategy=InheritanceType.JOINED)
//@Table(name="person")
public class Person extends DomainObject implements PersonActor, Persistent, XML {
    
    /** An empty person object */
    public static final Person EMPTY = new Person();
    
    /* The unique database id for the object */
    //@Id @GeneratedValue(strategy=GenerationType.AUTO)
    //@Column(name = "person_id", unique = true, nullable = false)
    private Long id;
    
	/** Identity number */
    //@Column(name="identity_no", unique=false, nullable=true)
	private String identityNo;
    
	/** The persons first name */
    //@Column(name="first_name", unique=false, nullable=true)
	private String firstName;

	/** The persons last name or surname */
    //@Column(name="last_name", unique=false, nullable=true)
	private String lastName;
    
	/** The persons initials */
    //@Column(name="initials", unique=false, nullable=true)
	private String initials;
    
	/** The persons gender */
    //@ManyToOne
    //@JoinColumn(name="gender_id")
	private Gender gender;
    
	/** Population group */
    //@ManyToOne
    //@JoinColumn(name="population_id")
	private PopulationGroup populationGroup;
    
	/** Date of birth */
    //@Column(name="dob", unique=false, nullable=true)
	private Date dateOfBirth;

    /** The entity information of the person. */
    //@OneToOne()
    //@JoinColumn(name="entity")
    private xperiment.metaphor.model.entity.Entity entity = new xperiment.metaphor.model.entity.Entity();

    /** Employee role of the person */
    //@OneToOne
    //@JoinColumn(name="employee_id")
    private Employee employee;

    /** User role of the person */
    //@OneToOne
    //@JoinColumn(name="user_id")
    private User user;

    /** The roles of the person */
    private SortedSet<Role> roles = new TreeSet<Role>();

    /** 
     * The version of the object in the persistent store, 
     * used for concurrent modification detection (a.k.a stale data)
     */
    private long version = 0;

    /** Enabled */
    //@Column(name="enabled", unique=false, nullable=true)
    private boolean enabled = true;

	/**
	 * Default constructor
	 */
	public Person() {
		super();
	}

	/**
	 * Constructs the person with the first name and last name
	 * @param firstName The first name
	 * @param lastName The last name
	 */
	public Person(String firstName, String lastName) {
		super();
        setFirstName(firstName);
        setLastName(lastName);
	}
	
	/**
	 * Constructs the person with the first name and last name
	 * @param firstName The first name
	 * @param lastName The last name
	 * @param initials The persons initials
	 */
	public Person(String firstName, String lastName, String initials) {
		this(firstName, lastName);
		setInitials(initials);
	}
	
	/**
	 * Constructs the person with the first name and last name
	 * @param firstName The first name
	 * @param lastName The last name
	 * @param initials The persons initials
	 * @param identityNo The persons identity number
	 * @param dateOfBirth The persons date of birth
	 */
	public Person(String firstName, String lastName, String initials, String identityNo, Date dateOfBirth) {
		this(firstName, lastName, initials);
		setIdentityNo(identityNo);
        setDateOfBirth(dateOfBirth);
	}

    /**
     * The unique database id for the object
     * @return Long
     */
	public Long getId() {
		return id;
	}

//	/**
//     * The unique database id for the object
//     * @param id The new value
//     */
//	public void setId(Long id) {
//		Long old = getId();
//		this.id = id;
//        firePropertyChange("id", old, getId());
//	}
	
    @Override
	public String getName() {
		return lastName + ", " + firstName;
	}
    
	/**
	 * The persons first name
	 * @return String
	 */
    
	public String getFirstName() {
		return firstName;
	}

	/**
	 * The persons first name
	 * @param firstName The new first name
	 */
	public void setFirstName(String firstName) {
        String old = getFirstName();
		this.firstName = firstName;
        firePropertyChange("firstName", old, getFirstName());
	}
	
	/**
	 * The persons last name
	 * @return String
	 */
	public String getLastName() {
		return lastName;
	}
	
	/**
	 * The persons last name
	 * @param lastName The new last name
	 */
	public void setLastName(String lastName) {
        String old = getLastName();
		this.lastName = lastName;
        firePropertyChange("lastName", old, getLastName());
	}
	
	/**
	 * The persons initials
	 * @return String
	 */
	public String getInitials() {
		return initials;
	}

	/**
	 * The persons initials
	 * @param initials The new initials
	 */
	public void setInitials(String initials) {
        String old = getInitials();
		this.initials = initials;
        firePropertyChange("initials", old, getInitials());
	}
	
	/**
	 * The persons date of birth
	 * @return Date
	 */
	public Date getDateOfBirth() {
		return dateOfBirth;
	}

	/**
	 * The persons date of birth
	 * @param dateOfBirth The new date of birth
	 */
	public void setDateOfBirth(Date dateOfBirth) {
        Date old = getDateOfBirth();
		this.dateOfBirth = dateOfBirth;
        firePropertyChange("dateOfBirth", old, getDateOfBirth());
	}

    /**
	 * Sets the date of birth
	 * @param year The year
	 * @param month The months January == 1
	 * @param day The day
	 */
    public void setDateOfBirth(int year, int month, int day) {

        if (year < 0) throw new IllegalArgumentException("The year cannot be negative");
        if (month < 1 || month > 12 ) throw new IllegalArgumentException("The month can only be between 1 and 12");
        if (day < 1 || day > 31) throw new IllegalArgumentException("The day can only be between 1 and 31");

        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.set(year, month - 1, day);
        setDateOfBirth(cal.getTime());
    }

	/**
	 * Sets the date of birth
	 * @param year The year
	 * @param month The months January == 1
	 * @param day The day
	 */
	public void dateOfBirth(int year, int month, int day) {
		Calendar cal = Calendar.getInstance();
        cal.clear();
		cal.set(year, month - 1, day);
		this.setDateOfBirth(cal.getTime());
	}
	
	/**
	 * The persons gender
	 * @return Gender
	 */
	public Gender getGender() {
		return gender;
	}

	/**
	 * The persons gender
	 * @param gender The new gender
	 */
	public void setGender(Gender gender) {
        Gender old = getGender();
		this.gender = gender;
        firePropertyChange("gender", old, getGender());
	}
	
	/**
	 * Identity number
	 * @return String
	 */
	public String getIdentityNo() {
		return identityNo;
	}

	/**
	 * Identity number
	 * @param identityNo The new identity number
	 */
	public void setIdentityNo(String identityNo) {
        String old = getIdentityNo();
		this.identityNo = identityNo;
        firePropertyChange("identityNo", old, getIdentityNo());
	}

	/**
	 * The persons population group
	 * @return PopulationGroup
	 */
	public PopulationGroup getPopulationGroup() {
		return populationGroup;
	}

	/**
	 * The persons population group
	 * @param populationGroup The new population group
	 */
	public void setPopulationGroup(PopulationGroup populationGroup) {
		PopulationGroup old = getPopulationGroup();
        this.populationGroup = populationGroup;
        firePropertyChange("populationGroup", old, getPopulationGroup());
	}

    /**
     * Returns the entity information of this person
     * @return Entity
     */
    protected xperiment.metaphor.model.entity.Entity getEntity() {
        if (this.entity == null) {
            this.entity = new xperiment.metaphor.model.entity.Entity();
        }
        return this.entity;
    }

    /**
     * Gets the person's telephone number
     * @return String
     */
    public String getTelephoneNo() {
        return this.getEntity().getContactInformation().getTelephoneNo();
    }

    /**
     * Gets the person's cellphone number
     * @return String
     */
    public String getCellphoneNo() {
        return this.getEntity().getContactInformation().getCellphoneNo();
    }

    /**
     * Gets the person's facsimile number
     * @return String
     */
    public String getFacsimile() {
        return this.getEntity().getContactInformation().getFacsimileNo();
    }

    /**
     * Gets the person's email
     * @return String
     */
    public String getEmail() {
        return this.getEntity().getContactInformation().getEmail();
    }

    /**
     * Gets the person's postal address
     * @return Address
     */
    public Address getPostalAddress() {
        return this.getEntity().getContactInformation().getPostalAddress();
    }

    /**
     * Gets the person's physical address
     * @return Address
     */
    public Address getPhysicalAddress() {
        return this.getEntity().getContactInformation().getPhysicalAddress();
    }

    /**
     * Whether the person is enabled or active in the system
     * @return boolean
     */
    public boolean isEnabled() {
		return enabled;
	}

    /**
     * Whether the person is enabled or active in the system
     * @param enabled The new value
     */
	public void setEnabled(boolean enabled) {
		boolean old = this.enabled;
		this.enabled = enabled;
		firePropertyChange("enabled", old, this.enabled);
	}

    /**
     * The version of the object in the persistent store.
     * @return long
     */
    public long getVersion() {
        return version;
    }

    /**
     * Whether the person is an employee.
     * @return boolean
     */
    public boolean isEmployee() {
        return (getEmployee() != null && getEmployee().getTerminated() == null);
    }

    /**
     * Whether the person is a user.
     * @return boolean
     */
    public boolean isUser() {
        return (getUser() != null && getUser().isEnabled());
    }

    /**
     * The employee role / link of the person
     * @return Employee
     */
    public Employee getEmployee() {
        return employee;
    }

    /**
     * The employee role / link of the person
     * @param employee The new value
     */
    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

    /**
     * The user role of the person
     * @return User
     */
    public User getUser() {
        return user;
    }

    /**
     * The user role of the person
     * @param user The new value
     */
    public void setUser(User user) {
        this.user = user;
    }

    /**
	 * The persons first name
	 * @param firstName The new first name
     * @return Person
	 */
    public Person firstName(String firstName) {
        this.setFirstName(firstName);
        return this;
    }

    /**
	 * The persons last name
	 * @param lastName The new last name
     * @return Person
	 */
    public Person lastName(String lastName) {
        this.setLastName(lastName);
        return this;
    }

    /**
	 * The persons initials
	 * @param initials The new value
     * @return Person
	 */
    public Person initials(String initials) {
        this.setInitials(initials);
        return this;
    }

    /**
	 * The persons identity or social security number
	 * @param identityNo The new value
     * @return Person
	 */
    public Person identityNo(String identityNo) {
        this.setIdentityNo(identityNo);
        return this;
    }

    /**
	 * Sets the date of birth
	 * @param year The year
	 * @param month The months January == 1
	 * @param day The day
     * @return Person
	 */
    public Person dob(int year, int month, int day) {
        this.setDateOfBirth(year, month, day);
        return this;
    }

    /**
     * Sets the person's gender to male
     * @return Person
     */
    public Person male() {
        this.setGender(Gender.MALE);
        return this;
    }

    /**
     * Sets the person's gender to female
     * @return Person
     */
    public Person female() {
        this.setGender(Gender.FEMALE);
        return this;
    }

    /**
     * Sets the person's population group to WHITE
     * @return Person
     */
    public Person white() {
        this.setPopulationGroup(PopulationGroup.WHITE);
        return this;
    }

    /**
     * Sets the person's population group to WHITE
     * @return Person
     */
    public Person black() {
        this.setPopulationGroup(PopulationGroup.BLACK);
        return this;
    }

    /**
     * Sets the person's population group to INDIAN
     * @return Person
     */
    public Person indian() {
        this.setPopulationGroup(PopulationGroup.INDIAN);
        return this;
    }

    /**
     * Sets the person's population group to COLORED
     * @return Person
     */
    public Person colored() {
        this.setPopulationGroup(PopulationGroup.COLORED);
        return this;
    }

    /**
     * Sets the person's population group to ETHNIC
     * @return Person
     */
    public Person ethnic() {
        this.setPopulationGroup(PopulationGroup.ETHNIC);
        return this;
    }

    /**
     * Sets the person's population group to ASIAN
     * @return Person
     */
    public Person asian() {
        this.setPopulationGroup(PopulationGroup.ASIAN);
        return this;
    }

    /**
     * Sets the person's telephone number
     * @param telephoneNo The new value
     * @return Person
     */
    public Person telephone(String telephoneNo) {
        this.getEntity().getContactInformation().setTelephoneNo(telephoneNo);
        return this;
    }

    /**
     * Sets the person's cellphone number
     * @param cellphoneNo The new value
     * @return Person
     */
    public Person cellphone(String cellphoneNo) {
        this.getEntity().getContactInformation().setCellphoneNo(cellphoneNo);
        return this;
    }

    /**
     * Sets the person's facsimile number
     * @param facsimileNo The new value
     * @return Person
     */
    public Person facsimile(String facsimileNo) {
        this.getEntity().getContactInformation().setFacsimileNo(facsimileNo);
        return this;
    }

    /**
     * Sets the person's email
     * @param email The new value
     * @return Person
     */
    public Person email(String email) {
        this.getEntity().getContactInformation().setEmail(email);
        return this;
    }

    /**
     * Sets the person's postal address
     * @param postalAddress The new value
     * @return Person
     */
    public Person postalAddress(Address postalAddress) {
        this.getEntity().getContactInformation().setPostalAddress(postalAddress);
        return this;
    }

    /**
     * Sets the person's physical address
     * @param physicalAddress The new value
     * @return Person
     */
    public Person physicalAddress(Address physicalAddress) {
        this.getEntity().getContactInformation().setPhysicalAddress(physicalAddress);
        return this;
    }

    /**
     * Sets the person's enabled status to true
     * @return Person
     */
    public Person activate() {
        this.setEnabled(true);
        return this;
    }

    /**
     * Sets the person's enabled status to false
     * @return Person
     */
    public Person deactivate() {
        this.setEnabled(false);
        return this;
    }

    /**
     * Sets the user role of the person. This also grants the person the
     * employee role.
     *
     * @param userName The username of the person
     * @param password The password of the user
     * @return Person
     */
    public Person user(String userName, String password) {

        if (this.user == null) {
            this.user = new User(userName, password);
        } else {
            this.user.setUsername(userName);
            this.user.setPassword(password);
        }

        /* Grant the user role */
        grant(this.user.getRole());

        return this;
    }

    /**
     * Sets the employee role of the person. This also grants the person the
     * employee role. The employment date is set to today.
     *
     * @param employeeNo The employee number of the person
     * @return Person
     */
    public Person employee(String employeeNo) {
        Calendar cal = Calendar.getInstance();
        DateUtils.truncate(cal, Calendar.DATE);

        return employee(employeeNo, cal.getTime());
    }

    /**
     * Sets the employee role of the person. This also grants the person the
     * employee role.
     *
     * @param employeeNo The employee number of the person
     * @param joined The person's employment date
     * @return Person
     */
    public Person employee(String employeeNo, Date joined) {

        /* Set the employee values */
        if (this.employee == null) {
            this.employee = new Employee();
        }

        this.employee.setEmployeeNo(employeeNo);
        this.employee.joined(joined);

        /* Grant the employee role */
        grant(this.employee.getRole());

        return this;
    }

    /**
     * Appoints the person to the position and sets the appointment date to today
     * @param position The position to appoint the person to
     * @return Person
     * @throws ModelException If there was an error with appointing the person to a position
     */
    public Person appoint(Position position) throws ModelException {
        Calendar cal = Calendar.getInstance();
        DateUtils.truncate(cal, Calendar.DATE);

        return appoint(position, cal.getTime());
    }

    /**
     * Appoints the person to the position and sets the appointment date.<br />
     *
     * <strong>Note: This method requires that the applicationManager be set.</strong>
     *
     * @param position The position to appoint to
     * @param appointmentDate The date of the appointment
     * @return Person
     * @throws ModelException If there was an error with appointing the person to a position
     */
    public Person appoint(Position position, Date appointmentDate) throws ModelException {

        if (!isEmployee()) {
            throw new NotEmployeeException("Cannot appoint non-employee to a position");
        }

        this.employee = this.employee.appoint(position, appointmentDate);
        return this;
    }

    /**
     * Sets this person's employment report to. Both this persons must be employees for the
     * relationship to be set.<br />
     * <strong>Note: This method requires that the applicationManager be set.</strong>
     * @param person The person that this person reports to
     * @return Person
     * @throws ModelException If there was an error with setting the reporting
     */
    public Person reportsTo(Person person) throws ModelException {

        if (person == null) {
            throw new ReportsToEmployeeException("The person to report to is null");
        } else if (!person.isEmployee()) {
            throw new NotEmployeeException("The person to report to is not an employee");
        }

        if (!isEmployee()) {
            throw new NotEmployeeException("This person is not an employee");
        }

        this.employee = this.employee.reportsTo(person.getEmployee());
        return this;
    }

    /** 
     * Grants a role to the person
     * @param role The role to add
     * @return Person
     */
    public Person grant(String role) {

        if (this.roles == null) {
            this.roles = new TreeSet<Role>();
        }

        Role r = new Role(role);
        if (!this.roles.contains(r)) {
            this.roles.add(r);
        } else {
            r = this.roles.subSet(r, r).first();
            if (r != null && !r.isEnabled()) {
                r.setEnabled(true);
            }
        }
        return this;
    }

    /**
     * Revokes the role from the person
     * @param role The role to revoke
     * @return Person
     */
    public Person revoke(String role) {

        if (this.roles == null) {
            this.roles = new TreeSet<Role>();
        }

        Role r = new Role(role);
        if (!this.roles.contains(r)) {
            /* Do nothing since the role is not present */
        } else {
            r = this.roles.subSet(r, r).first();
            if (r != null && r.isEnabled()) {
                r.setEnabled(false);
            }
        }
        return this;
    }

    /**
     * Determines if the person has the role
     * @param role The role to check for
     * @return boolean
     */
    public boolean hasRole(String role) {

        if (this.roles == null) {
            this.roles = new TreeSet<Role>();
        }

        return this.roles.contains(new Role(role));
    }

    /**
     * Returns the roles of the person
     * @return String[]
     */
    public String[] roles() {
        if (this.roles == null) {
            this.roles = new TreeSet<Role>();
        }

        String[] roles = new String[this.roles.size()];
        SortedSet<Role> r = Collections.synchronizedSortedSet(this.roles);
        synchronized(r) {
            int index = 0;
            for (Role role : r) {
                roles[index++] = role.getName();
            }
        }
        return roles;
    }

    /**
     * Makes the person a contact of this person
     * @param person The person to make a contact of this person
     * @return Person
     */
    public Person contact(Person person, ContactType contactType) {
        this.getEntity().getRelationships().add(new Contact(person.getEntity(), contactType));
        return this;
    }

    /**
     * Retrieves the unique person by user name.
     * @param username The user name of the person
     * @return Person
     * @throws xperiment.metaphor.model.UniqueResultException If more than one person has the username
     * @throws xperiment.metaphor.model.RetrieveException If there was an error
     */
    public static Person uniqueByUserName(ApplicationManager applicationManager, String username) throws RetrieveException {
        Query query = Query.forClass(Person.class)
                 .innerJoin("user", "u")
                 .eq("u.username", username);

        Dao dao = null;
        
        try {
            dao = (Dao) applicationManager.getDao();
        } catch (ServiceException e) {
            throw new RetrieveException("Unable to retrieve person for username", e);
        }

        List values = dao.retrieve(query);
        if (values == null) {
            return null;
        } else if (values.size() == 1) {
            return (Person) values.get(0);
        } else if (values.size() == 0) {
            return null;
        } else {
            throw new UniqueResultException("More than one person found for username `" + username + "`");
        }
    }

    /**
     * 
     * @param applicationManager
     * @return
     * @throws RetrieveException
     */
    public static List<Person> retrieveUsers(ApplicationManager applicationManager) throws RetrieveException {
        Query query = Query.forClass(Person.class)
                 .innerJoin("user", "u");

        Dao dao = null;
        
        try {
            dao = (Dao) applicationManager.getDao();
        } catch (ServiceException e) {
            throw new RetrieveException("Unable to retrieve users", e);
        }

        List values = dao.retrieve(query);
        if (values == null) {
            return new ArrayList<Person>();
        } else {
            return (List<Person>) values;
        }
    }

    /**
     * 
     * @param applicationManager
     * @return
     * @throws RetrieveException
     */
    public static List<Person> retrieveEmployees(ApplicationManager applicationManager) throws RetrieveException {
        Query query = Query.forClass(Person.class)
                 .innerJoin("employee", "e");

        Dao dao = null;
        
        try {
            dao = (Dao) applicationManager.getDao();
        } catch (ServiceException e) {
            throw new RetrieveException("Unable to retrieve employees", e);
        }

        List values = dao.retrieve(query);
        if (values == null) {
            return new ArrayList<Person>();
        } else {
            return (List<Person>) values;
        }
    }

    /**
     * Retrieves the unique person by user name.
     * @param username The user name of the person
     * @return Person
     * @throws xperiment.metaphor.model.UniqueResultException If more than one person has the username
     * @throws xperiment.metaphor.model.RetrieveException If there was an error
     */
    public Person uniqueByUserName(String username) throws RetrieveException {
        return uniqueByUserName(getApplicationManager(), username);
    }

    /**
     * Saves the person to the persistent store
     * @throws xperiment.metaphor.model.ModelException If there was an error
     */
    public Person save() throws ModelException {

        /*
         * Persist the employee role
         */
        if (getEmployee() != null) {
            getEmployee().attach(getApplicationManager());
            getEmployee().save();
        }

        /*
         * Persist the user role
         */
        if (getUser() != null) {
            getUser().attach(getApplicationManager());
            getUser().save();
        }

        /*
         * Persist the person
         */
        if (getGender() != null) {
            getGender().attach(getApplicationManager());
            getGender().save();
        }

        if (getPopulationGroup() != null) {
            getPopulationGroup().attach(getApplicationManager());
            getPopulationGroup().save();
        }

        /* Save the entity information */
        getEntity().save(getApplicationManager());

        /* Save the person's roles */
        if (this.roles != null) {
            for (Role role : roles) {
                role.attach(getApplicationManager());
                role.save();
            }
        }

        try {
            Dao dao = getDao();
            Person p = (Person) dao.persist(this);
            this.id = p.getId();

            return this;
        } catch (DataAccessException e) {
            throw ModelUtils.convertDataAccessException(e);
        } catch (ServiceException e) {
            throw new SaveException("Unable to save person", e);
        }
    }

    /**
     * Returns the XML representation of the person.<br />
     * @return String
     */
    public String toXML() {

        return "<person='" + getClass().getName()
                + "' id='" + getId()
                + "' identity_no='" + getIdentityNo()
                + "' first_name='" + getFirstName()
                + "' last_name='" + getLastName()
                + "' initials='" + getInitials()
                + "' dob='" + (getDateOfBirth() != null ? new SimpleDateFormat("yyyy-MM-dd").format(getDateOfBirth()) : "")
                + "' enabled='" + isEnabled()
                + "' >"
                + (getGender() != null ? getGender().toXML() : "")
                + (getPopulationGroup() != null ? getPopulationGroup().toXML() : "")
                + (getEmployee() != null ? getEmployee().toXML() : "")
                + (getUser() != null ? getUser().toXML() : "")
                + "</person>";
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (o == this) {
            return true;
        }
        if (o.getClass() != getClass()) {
            return false;
        }
        Person rhs = (Person) o;
        return new EqualsBuilder()
                .append(identityNo, rhs.getIdentityNo())
                .append(firstName, rhs.getFirstName())
                .append(lastName, rhs.getLastName())
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 41)
                .append(identityNo)
                .append(firstName)
                .append(lastName)
                .toHashCode();
    }

    @Override
    public String toString() {
        return ToStringBuilder
                .reflectionToString(
                        this,
                        ToStringStyle.MULTI_LINE_STYLE, true,
                        Person.class);
    }
}
